package tv.geir.slackspace.server;

import java.io.IOException;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectFactory;

import tv.geir.slackspace.common.ConnectionException;

/**
 * 
 * Handles all connections to clients and dispatches requests to the appropriate
 * SlackSpace.
 * 
 * @author Geir Gullestad Pettersen
 *
 */
public class ServerConnector{

	private Log log = LogFactory.getLog( ServerConnector.class );
	
	private int serverPort = 8700;
	
	private int concurrentClients = 2;
	
	private Boolean serverRunning = Boolean.FALSE;
	
	private ExecutorService threads;
	
	private ServerSocket server;
	
	private List<ISocketHandler> clients;
	
	private ObjectFactory socketHandlerFactory;
	

	/**
	 * Creates a ServerConnector
	 * @param requestProcessor the executor that should be used to execute requests.
	 */
	public ServerConnector() {
		
		clients = new ArrayList<ISocketHandler>();
	}
	
	/**
	 * Initiates the connector
	 */
	public void init() {
		threads = Executors.newFixedThreadPool( concurrentClients );		
		
		startThreadedConnector();
	}

	
	/**
	 * Spawns a new thread in which the server connector will open a server socket
	 * and accept incoming connections from clients.
	 *
	 */
	public void startThreadedConnector() {
		// set server state
		synchronized ( serverRunning ) {
			if( serverRunning == true ) {
				throw new IllegalStateException( "Server already running and accepting clients" );
			}
			serverRunning = true;
		}
		
		// start threaded server	
		Thread t = new Thread( new Runnable() {
			public void run() {
				acceptClients();
			}
		});
		
		try {
			server = new ServerSocket( serverPort );
		} catch (IOException e) {
			log.fatal( "Error initiating connector: " + e.getMessage(), e );
		}
		log.info( "ServerConnector now accepting clients");
		
		t.setName("ServerConnector");
		t.start();			
	}

	/**
	 * Creates server socket and accepts clients
	 *
	 */
	private void acceptClients() {
			
			try {
				
				while( serverRunning ) {
					// wait for incoming connection
					Socket connection = server.accept();
					
					// create a handler for the connection
					ISocketHandler handler = (ISocketHandler) socketHandlerFactory.getObject(); 					
					handler.setSocket( connection );
					
					// save reference to client 
					clients.add( handler );
					
					// put handler in executor queue.
					threads.execute( handler );
				}
				
			}catch( BindException be ) {
				throw new ConnectionException( be );
			}			
			catch (Exception e) {
				if( server.isClosed() ) {
					log.debug( "Serversocket closed" );
				} else {
					log.error( "Error handling socket", e );
					throw new ConnectionException( e );
				}
			} finally {
				log.info( "ServerConnector stopped" );
			}
			
		
	}
	
	/**
	 * Shuts down the server socket and all open connections to the clients. Executing 
	 * client threads are stopped.
	 *
	 */
	public void stopConnector() {
		serverRunning = false;
		try {
			
			server.close();
			closeClientConnections();
			threads.shutdownNow();
		} catch (IOException e) {
			throw new ConnectionException( e );
		}
	}
	
	private void closeClientConnections() {
		for( ISocketHandler sh : clients ) {
			sh.closeConnection();
		}
	}
	public int getServerPort() {
		return serverPort;
	}

	public void setServerPort(int serverPort) {
		this.serverPort = serverPort;
	}

	public int getConcurrentClients() {
		return concurrentClients;
	}

	public void setConcurrentClients(int threadPoolSize) {
		this.concurrentClients = threadPoolSize;
	}
	
	public ObjectFactory getSocketHandlerFactory() {
		return socketHandlerFactory;
	}
	
	public void setSocketHandlerFactory(ObjectFactory socketHandlerFactory) {
		this.socketHandlerFactory = socketHandlerFactory;
	}
}
